//#include "stdafx.h"
#include "../../../BGAudioEngine/stdafx.h"
#include "AudioHostForDS.h"

#ifdef DEBUG
#include <fstream>
static std::ofstream f("t.wav",std::ios::binary);
#endif

#define MY_RELEASE(p)	{if(p){p->Release();p=NULL;}}

INT_PTR CALLBACK CAudioHostForDS::DSoundEnumCallbackC( GUID* pGUID, TCHAR* strDesc, TCHAR* strDrvName,VOID* pContext )
{
	CAudioHostForDS* po=(CAudioHostForDS*)pContext;
#ifdef UNICODE
	DWORD dwNum = WideCharToMultiByte(CP_OEMCP,NULL,strDesc,-1,NULL,0,NULL,FALSE);
	WideCharToMultiByte (CP_OEMCP,NULL,strDesc,-1,po->m_pszCDevNames[po->m_nCDevs],dwNum,NULL,FALSE);
#else
	strcpy(po->m_pszCDevNames[po->m_nCDevs],strDesc);
#endif
	if(pGUID==NULL)
	{
		po->m_iCurCDev=po->m_nCDevs;
		memcpy(&(po->guidCDev[po->m_iCurCDev]),&GUID_NULL,sizeof(GUID));
	}
	else
	{
		memcpy(&(po->guidCDev[po->m_nCDevs]),pGUID,sizeof(GUID));
	}
	++po->m_nCDevs;
	return TRUE;
}

CAudioHostForDS::CAudioHostForDS(void)
	: m_nCDevs(0)
	, m_iCurCDev(0)
	, m_pVSTArray(NULL)
	, m_hThread(NULL)
	, m_nBufferSize(441*40*4)
	, m_iInChannels(2)
	, m_nVolume(100)
	, m_nVST(0)
	, MAX_BUFFER_SIZE(128000)
	, MIN_BUFFER_SIZE(128)
	, m_eDrvType(DIRECT_SOUND)
	, m_pDSC(NULL)
	, m_pDscb(NULL)
	, m_pDscb8(NULL)
	, m_fVstChanged(FALSE)
{
	HRESULT hr=DS_OK;
	for(int i=0;i<16;++i){
		memcpy(&guidCDev[i],&GUID_NULL,sizeof(GUID));
		memset(&m_pszCDevNames[i][0],0,128);
	}
	
	hr=DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)DSoundEnumCallbackC,(LPVOID)this);
	
	if(FAILED(hr))
	{
		m_fInited=false;
	}

	InitializeCriticalSection (&m_cs) ;

	for(int i=0;i<2;++i){
		m_hCapBufferEvent[i]=CreateEvent(NULL,FALSE,FALSE,NULL);
		m_hCtlEvent[i]=CreateEvent(NULL,FALSE,FALSE,NULL);
	}

	m_wfx.Format.cbSize=0;
	m_wfx.Format.wBitsPerSample=16;
	m_wfx.Format.nChannels=2;
	m_wfx.Format.nBlockAlign=m_wfx.Format.wBitsPerSample*m_wfx.Format.nChannels/8;
	m_wfx.Format.nSamplesPerSec=44100;
	m_wfx.Format.nAvgBytesPerSec=m_wfx.Format.nBlockAlign*m_wfx.Format.nSamplesPerSec;
	m_wfx.Format.wFormatTag=WAVE_FORMAT_PCM;
}

CAudioHostForDS::~CAudioHostForDS(void)
{
	if(m_pVSTArray)
	{
		free(m_pVSTArray);
		m_pVSTArray=NULL;
		m_nVST=0;
	}
	DeleteCriticalSection (&m_cs) ;

	for(int i=0;i<2;++i){
		if(m_hMainBufferEvent[i])
		{
			CloseHandle(m_hCapBufferEvent[i]);
			m_hCapBufferEvent[i]=NULL;
		}
		if(m_hCtlEvent[i])
		{
			CloseHandle(m_hCtlEvent[i]);
			m_hCtlEvent[i]=NULL;
		}
	}
}

BOOL CAudioHostForDS::Init()
{
	if(!CDsoundOutput::Init())
	{
		return FALSE;
	}
	HRESULT hr=DS_OK;
	if(IsEqualGUID(guidDev[m_iCurCDev],GUID_NULL))
	{
		hr=DirectSoundCaptureCreate8( NULL, &m_pDSC, NULL );
	}
	else
	{
		hr=DirectSoundCaptureCreate8(&guidCDev[m_iCurCDev], &m_pDSC, NULL );
	}
	
	if(SUCCEEDED(hr))
	{
		ZeroMemory(&m_dscbdesc,sizeof(m_dscbdesc));
		if(m_fEnable3D)
		{
			//Add 3D buffer creation
		}
		if(!m_fEnable3D)
		{
			m_dscbdesc.dwSize = sizeof(DSCBUFFERDESC); 
			m_dscbdesc.dwFlags = DSCBCAPS_WAVEMAPPED; 
			m_dscbdesc.dwBufferBytes = 2*m_nBufferSize;
			m_dscbdesc.lpwfxFormat = (WAVEFORMATEX*)&m_wfx; 

			HRESULT hr = m_pDSC->CreateCaptureBuffer(&m_dscbdesc, &m_pDscb, NULL);
			if(FAILED(hr))
			{
				return FALSE;
			}
			hr = m_pDscb->QueryInterface(IID_IDirectSoundCaptureBuffer8, (LPVOID*) &m_pDscb8);
			if(m_pDscb8==NULL)
			{
				return FALSE;
			}
			MY_RELEASE(m_pDscb);

			LPDIRECTSOUNDNOTIFY8 lpDsNotify;
			DSBPOSITIONNOTIFY PositionNotify[2];
			
			if (SUCCEEDED(hr = m_pDscb8->QueryInterface(IID_IDirectSoundNotify8,(LPVOID*)&lpDsNotify))) 
			{
				PositionNotify[0].dwOffset = 0;//m_dwBufferSize>>1;
				PositionNotify[0].hEventNotify = m_hCapBufferEvent[0];
				PositionNotify[1].dwOffset = m_dwBufferSize;//m_dwBufferSize+(m_dwBufferSize>>1);
				PositionNotify[1].hEventNotify = m_hCapBufferEvent[1];

				hr = lpDsNotify->SetNotificationPositions(2, PositionNotify);
				MY_RELEASE(lpDsNotify);
			}
		}
	}

	if(SUCCEEDED(hr))
	{
		return TRUE;
	}
	return FALSE;
}

void CAudioHostForDS::Deinit()
{
	CDsoundOutput::Deinit();
	if(m_fEnable3D)
	{
		//Add 3D buffer releasing
	}
	if(!m_fEnable3D)
	{
		MY_RELEASE(m_pDscb8);
	}
	MY_RELEASE(m_pDSC);

	for(int i=0;i<2;++i){
		ResetEvent(m_hCapBufferEvent[i]);
		ResetEvent(m_hCtlEvent[i]);
	}
}

BOOL CAudioHostForDS::SetCurrentListenDevice(int i)
{
	if(m_fIsRun||i<0||i>=m_nLDevs)
		return FALSE;
	return SetDevice(i);
}

BOOL CAudioHostForDS::SetCurrentListenDeviceByName(char* szDeviceName)
{
	for(int i=0;i<m_nLDevs;++i){
		if(stricmp(szDeviceName,m_pszLDevNames[i])==0){
			return SetCurrentListenDevice(i);
		}
	}
	return FALSE;
}

BOOL CAudioHostForDS::SetCurrentCaptureDevice(int i)
{
	if(m_fIsRun||i<0||i>=m_nCDevs)
		return FALSE;
	m_iCurCDev=i;
	return TRUE;
}

BOOL CAudioHostForDS::SetCurrentCaptureDeviceByName(char* szDeviceName)
{
	for(int i=0;i<m_nCDevs;++i){
		if(stricmp(szDeviceName,m_pszCDevNames[i])==0){
			return SetCurrentCaptureDevice(i);
		}
	}
	return FALSE;
}

#define RUN_E 0
#define STOP_E 1

DWORD WINAPI CAudioHostForDS::CreateWorkThread(LPVOID pParam)
{
	CAudioHostForDS* p=(CAudioHostForDS*)pParam;
	return p->DoWork();
}

DWORD CAudioHostForDS::DoWork()
{
	if(CAudioHost::pMixer)
	{
		CAudioHost::SetSampleRate(m_wfx.Format.nSamplesPerSec);
		CAudioHost::SetBufferSize(m_nBufferSize/m_wfx.Format.nBlockAlign);
	}

	HRESULT hr=S_OK;
	PBYTE pBuffer=new BYTE[m_nBufferSize];
	CWavVstHost** pVSTs=NULL;
	int nVSTs=0;

	EnterCriticalSection (&m_cs);
	if(m_fVstChanged)
	{
		pVSTs=m_pVSTArray;
		nVSTs=m_nVST;
		if(pVSTs){
			for(int i=0;i<nVSTs;++i)
			{
				pVSTs[i]->SetBuffer(m_wfx.Format.nSamplesPerSec,m_nBufferSize/m_wfx.Format.nBlockAlign);
			}
		}
		m_fVstChanged=FALSE;
	}
	LeaveCriticalSection (&m_cs);

	int NBS=m_wfx.Format.wBitsPerSample/8;//N Bytes Per Sample, no bigger than 4;
	if(NBS>4||NBS<1)
		return -1;

	DWORD BitMask=0;
	switch(NBS)
	{
		case 1:
			BitMask=0x000000ff;
			break;
		case 2:
			BitMask=0x0000ffff;
			break;
		case 3:
			BitMask=0x00ffffff;
			break;
		case 4:
			BitMask=0xffffffff;
			break;
	}

	DWORD NgtMask=0;//Negative
	switch(NBS)
	{
		case 1:
			NgtMask=0xffffff00;
			break;
		case 2:
			NgtMask=0xffff0000;
			break;
		case 3:
			NgtMask=0xff000000;
			break;
		case 4:
			NgtMask=0x00000000;
			break;
	}

	BOOL fDone=FALSE;
	HANDLE hEvents[4]={m_hCapBufferEvent[0],m_hCapBufferEvent[1],m_hCtlEvent[0],m_hCtlEvent[1]};
	DWORD dwWait=0;

	PVOID b1=NULL,b2=NULL;
	DWORD l1=0,l2=0;
	DWORD pc=0,pr=0;
	DWORD ib=0;
	DWORD dwTimeOut=m_nBufferSize*1000/m_wfx.Format.nAvgBytesPerSec;

	while(!fDone)
	{
		dwWait=WaitForMultipleObjects(4, hEvents, FALSE, INFINITE);
		if ((dwWait == WAIT_FAILED) || (dwWait == WAIT_TIMEOUT))
			break;
		dwWait -= WAIT_OBJECT_0;
		if(dwWait<2)
		{
			m_pDscb8->GetCurrentPosition(&pc,&pr);
			if(pc>m_nBufferSize)
			{
				m_pDscb8->Lock(0,m_nBufferSize,&b1,&l1,&b2,&l2,0);
			}
			else
			{
				m_pDscb8->Lock(m_nBufferSize,m_nBufferSize,&b1,&l1,&b2,&l2,0);
			}
			memcpy(pBuffer,b1,l1);
			if(l2)
			{
				memcpy(&pBuffer[l1],b2,l2);
			}
			m_pDscb8->Unlock(b1,l1,b2,l2);

			BYTE* pB=(BYTE*)pBuffer;
			long tS=0;
			EnterCriticalSection (&m_cs);
			for(int i=0;i<m_nBufferSize;i+=NBS){
				tS=(long)(((DWORD)(*((long*)(&pB[i]))))&BitMask);
				if((long(((DWORD)tS)<<((4-NBS)<<3)))<0)
				{
					tS|=NgtMask;
				}
				tS*=((long)m_nVolume);
				tS/=100;
				for(int j=0;j<NBS;++j)
				{
					pB[i+j]=((BYTE*)&tS)[j];
				}
				//pB[i]=(short)(((long)pB[i])*((long)pAH->m_nVolume)/100);
			}
			if(m_fVstChanged)
			{
				pVSTs=m_pVSTArray;
				nVSTs=m_nVST;
				if(pVSTs){
					for(int i=0;i<nVSTs;++i)
					{
						pVSTs[i]->SetBuffer(m_wfx.Format.nSamplesPerSec,m_nBufferSize/m_wfx.Format.nBlockAlign);
					}
				}
				m_fVstChanged=FALSE;
			}
			if(pVSTs){
				for(int i=0;i<nVSTs;++i)
				{
					pVSTs[i]->Process(pBuffer);
				}
			}
			LeaveCriticalSection (&m_cs);
			/////////////////////////////////////////////////////
			switch(m_wfx.Format.nBlockAlign)
			{
				case 2:
					CAudioHost::HandleSamples8Bit((char*)pBuffer,m_nBufferSize);
					break;
				case 4:
					CAudioHost::HandleSamples16Bit((char*)pBuffer,m_nBufferSize);
					break;
				case 6:
					break;
				case 8:
					CAudioHost::HandleSamples32Bit((char*)pBuffer,m_nBufferSize);
					break;
			}
			/////////////////////////////////////////////////////

#ifdef DEBUG
				f.write((char*)pBuffer,m_nBufferSize);
#endif

			if((ib=WaitForMultipleObjects(2,m_hMainBufferEvent,FALSE,dwTimeOut))!=WAIT_TIMEOUT)
			{
				ib-=WAIT_OBJECT_0;
				FillMainBuffer(pBuffer,ib);
			}
			
		}
		else
		{
			dwWait-=2;
			if(dwWait==RUN_E)
			{
				hr=m_pDscb8->Start(DSCBSTART_LOOPING);
			}
			else if(dwWait==STOP_E)
			{
				hr=m_pDscb8->Stop();
				fDone=TRUE;
			}
			else
			{
			}
		}
	}

	if(pBuffer){
		delete []pBuffer;
		pBuffer=NULL;
	}
	return 0;
}

BOOL CAudioHostForDS::Start(void)
{
	HRESULT hr;
	if(m_fIsRun||!m_fInited)
		return FALSE;
	if(!Init())
	{
		Deinit();
		return FALSE;
	}
	m_hThread=CreateThread(NULL, 0, CreateWorkThread, this, 0, NULL);
	::SetThreadPriority(m_hThread,THREAD_PRIORITY_TIME_CRITICAL);
	CDsoundOutput::Start();
	SetEvent(m_hCtlEvent[RUN_E]);
	return TRUE;
}

void CAudioHostForDS::Stop(void)
{
	HRESULT hr;
	if(!m_fIsRun||!m_fInited)
		return;
	if(m_hThread)
	{
		SetEvent(m_hCtlEvent[STOP_E]);
		WaitForSingleObject(m_hThread,INFINITE);
		CloseHandle(m_hThread);
		m_hThread=NULL;
	}
	CDsoundOutput::Stop();
	Deinit();
	m_fIsRun=FALSE;
}
	
BOOL CAudioHostForDS::SetVstEffect(CWavVstHost** pVSTArray,int nCount)
{
	if(nCount<0)
	{
		return FALSE;
	}
	EnterCriticalSection (&m_cs);
	if(m_pVSTArray)
	{
		free(m_pVSTArray);
		m_pVSTArray=NULL;
		m_nVST=0;
	}
	if(nCount>0&&pVSTArray!=NULL)
	{
		m_pVSTArray=(CWavVstHost**)malloc(sizeof(CWavVstHost*)*nCount);
		m_nVST=nCount;
		for(int i=0;i<nCount;++i)
		{
			m_pVSTArray[i]=pVSTArray[i];
		}
	}
	m_fVstChanged=TRUE;
	LeaveCriticalSection (&m_cs);
	return TRUE;
}

BOOL CAudioHostForDS::SetBufferSize(int size)
{
	if(m_fIsRun)
		return FALSE;
	if(size>MAX_BUFFER_SIZE||size<MIN_BUFFER_SIZE)
		return FALSE;
	if(!CDsoundOutput::SetBufferSize(size))
		return FALSE;
	m_nBufferSize=size;
	return TRUE;
}

int CAudioHostForDS::SetVolume(int Vol)//0-100
{
	if(Vol>100)
		Vol=100;
	if(Vol<0)
		Vol=0;
	EnterCriticalSection (&m_cs);
	m_nVolume=Vol;
	LeaveCriticalSection (&m_cs);
	return Vol;
}

BOOL CAudioHostForDS::SetFormat(WAVEFORMATEX* pwfx)
{
	if(m_fIsRun)
	{
		return FALSE;
	}
	if(pwfx==NULL)
	{
		return FALSE;
	}
	memcpy(&m_wfx.Format,pwfx,sizeof(WAVEFORMATEX));
	BOOL rt=Init();
	Deinit();
	return rt;
}

void CAudioHostForDS::GetFormat(WAVEFORMATEX* pwfx)
{
	if(pwfx)
	{
		memcpy(pwfx,&m_wfx.Format,sizeof(WAVEFORMATEX));
	}
}